﻿using LaunchAllaround.DesignPattern;
using LaunchAllaround.DesignPattern.MVC;
using LaunchAllaround.Key;
using System;
using System.Collections;
using System.Windows.Media;
using System.Windows.Media.Media3D;


namespace LaunchAllaround.Pad
{
    [Serializable]
    public class PadModel : Container<KeyModel>, NamedModel<PadModel>, Resettable<PadModel>
    {

        public static readonly int MaxRows = 3;
        public static readonly int MaxColumns = 4;

        public static readonly int preferedRows = 2;
        public static readonly int preferedColumns = 3;

        public PadModel() 
        {
            this.init();
            this.AddView(new PadUI(this));
            this.AddView(new PadMenu(this));
        }
        public PadModel(PadUI pui)
        {
            this.init();
            this.AddView(pui);
            this.AddView(new PadMenu(this));
        }

        public PadModel(PadMenu pm)
        {
            this.init();
            this.AddView(new PadUI(this));
            this.AddView(pm);
        }

        private void init()
        {
            this.color = Colors.AntiqueWhite;
            this.Center = new Point3D(0, 0, 0);
            this.Width = 800;
            this.Height = 800;
            this.rows = PadModel.preferedRows;
            this.columns = PadModel.preferedColumns;
            this.resetKeys();
            this.modelFunc = new NamedModelImpl<PadModel>();
        }

        private void resetKeys()
        {
            this.Clear();
            int length = this.rows * this.columns;
            for (int i = 0; i < length; i++)
            {
                this.Add(new KeyModel());
            }
        }

        private Color color;
        public Color Color
        {
            get { return this.color; }
            set 
            { 
                this.color = value; 
                this.RefreshViews(new RefreshColor(this, this.color));
            }
        }

        private Point3D center;
        public Point3D Center
        {
            get{return this.center;}
            set{ this.center = value; }
        }

        public void SetCenter(float X, float Y, float Z)
        {
            this.Center = new Point3D(X, Y, Z);
        }

        private float width;
        public float Width
        {
            get{return this.width;}
            set{this.width = value;}
        }

        private float height;
        public float Height
        {
            get{ return this.height;}
            set{this.height = value;}
        }

        private int rows;
        public int Rows
        {
            get{return this.rows;}
            set 
            {
                if (value < 1 || value > PadModel.MaxRows)
                {
                    throw new RowCountException("rows",value);
                }

                // add rows
                if (value > this.rows)
                {
                    int count = (value - this.rows) * this.columns;
                    for (int i = 0; i < count; i++)
                    {
                        this.Add(new KeyModel());
                    }
                    Footer.myself.PrintInfo("Row added");
                }
                // remove rows
                else if (value < this.rows)
                {
                    int begin = this.items.Count - (this.rows - value) * this.columns;
                    int end = this.items.Count;
                    for (int i = end-1; i >= begin; i--)
                    {
                        this.RemoveAt(i);
                    }
                    Footer.myself.PrintInfo("Row removed");
                }
                this.rows = value;
                this.RefreshViews(new RefreshRowsColumns(this, this.rows, this.columns));
            }
        }

        private int columns;
        public int Columns
        {
            get{return this.columns;}
            set 
            {
                if (value < 1 || value > PadModel.MaxColumns)
                {
                    throw new ColumnCountException("columns", value);
                }

                // add columns 
                if (value > this.columns)
                {
                    int nbNewCols = value - this.columns;
                    for (int j = 1; j <= this.rows; j++)
                    {
                        for (int k = 0; k < nbNewCols; k++)
                        {
                            this.Insert(this.columns * j + k, new KeyModel());
                        }
                        Footer.myself.PrintInfo("Column added");
                    }
                }
                // remove columns
                else if (value < this.columns)
                {
                    int nbRemCols = this.columns - value;

                    for (int j = this.rows; j > 0; j--)
                    {
                        for (int k = this.columns * j - 1; k >= this.columns * j - nbRemCols; k--)
                        {
                            this.RemoveAt(k);
                        }
                    }
                    Footer.myself.PrintInfo("Column removed");
                }
                this.columns = value;
                this.RefreshViews(new RefreshRowsColumns(this, this.rows, this.columns));
            }
        }


        public KeyModel Get(int i, int j)
        {
            return this.Get(i + j * this.rows);
        }

        // # Model Functionalities ######
        private NamedModelImpl<PadModel> modelFunc;

        public void AddView(View<PadModel> v)
        {
            this.modelFunc.AddView(v);
        }

        public Container<View<PadModel>> GetViews()
        {
            return this.modelFunc.GetViews();
        }

        public void ClearViews()
        {
            this.modelFunc.ClearViews();
        }

        public void RefreshViews(RefreshEvent<PadModel> ev)
        {
            this.modelFunc.RefreshViews(ev);
        }

        public void SetName(string name)
        {
            this.modelFunc.SetName(name);
            this.RefreshViews(new RefreshName<PadModel>(this, this.GetName()));
        }

        public string GetName()
        {
            return this.modelFunc.GetName();
        }
        
        
        // ##############################

        public void Reset(PadModel pattern)
        {
            this.Center = pattern.Center;
            this.Color = pattern.Color;
            this.Columns = pattern.Columns;
            this.Rows = pattern.Rows;
            this.Width = pattern.Width;
            this.Height = pattern.Height;
            this.Rows = pattern.Rows;
            this.Columns = pattern.Columns;

            for (int i = 0; i < this.Count; i++)
            {
                this.Get(i).Reset(pattern.Get(i));
            }
        }
    }
}